///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_CCOLORSPACECONVERSION_H_
#define _BUOLA_IMAGE_CCOLORSPACECONVERSION_H_

#include <buola/image/cchannelconversion.h>

namespace buola { namespace img {

struct TColorSpace_rgb
{
    static const size_t cChannels=3;

    template<typename tType>
    static void Blend(tType *pDst,const tType *pSrc,uint8_t pCover)
    {
        pDst[0]+=(tType)((((int)pSrc[0]-pDst[0])*(pCover))>>8);
        pDst[1]+=(tType)((((int)pSrc[1]-pDst[1])*(pCover))>>8);
        pDst[2]+=(tType)((((int)pSrc[2]-pDst[2])*(pCover))>>8);
    }
};
    
struct TColorSpace_rgba 
{
    static const size_t cChannels=4;

    template<typename tType>
    static void Blend(tType *pDst,const tType *pSrc,uint8_t pCover)
    {
        ///\todo do it right
        pDst[0]+=(tType)((((int)pSrc[0]-pDst[0])*(pCover))>>8);
        pDst[1]+=(tType)((((int)pSrc[1]-pDst[1])*(pCover))>>8);
        pDst[2]+=(tType)((((int)pSrc[2]-pDst[2])*(pCover))>>8);
        pDst[3]+=(tType)((((int)pSrc[3]-pDst[3])*(pCover))>>8);
    }
};

struct TColorSpace_bgr
{
    static const size_t cChannels=3;
};

struct TColorSpace_bgra
{
    static const size_t cChannels=4;
    
    template<typename tType>
    static void Blend(tType *pDst,const tType *pSrc,uint8_t pCover)
    {
        ///\todo do it right
        pDst[0]+=(tType)((((int)pSrc[0]-pDst[0])*(pCover))>>8);
        pDst[1]+=(tType)((((int)pSrc[1]-pDst[1])*(pCover))>>8);
        pDst[2]+=(tType)((((int)pSrc[2]-pDst[2])*(pCover))>>8);
        pDst[3]+=(tType)((((int)pSrc[3]-pDst[3])*(pCover))>>8);
    }
};

struct TColorSpace_gray
{
    static const size_t cChannels=1;
};

struct TColorSpace_hsv
{
    static const size_t cChannels=3;
};

struct TColorSpace_xy
{
    static const size_t cChannels=2;
};

struct TColorSpace_xyz
{
    static const size_t cChannels=3;
};

template<typename tSrc,typename tDst>
struct MGetChannelConversion
{
    typedef CChannelConversion<typename ODecay<tSrc>::TChannel,typename ODecay<tDst>::TChannel> type;
};

template<typename tFrom,typename tTo>
struct CColorSpaceConversion;

template<typename tSpace>
struct CColorSpaceConversion<tSpace,tSpace>
{
    template<typename tSrc,typename tDst>
    void operator()(const tSrc &pSrc,tDst &&pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;

        for(int i=0;i<tSpace::cChannels;i++)
            pDst[i]=lChannelConv(pSrc[i]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgb,TColorSpace_hsv>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        CChannelConversion<typename tSrc::TChannel,TChannel_32f> lChannelConv;
        
        float r=lChannelConv(pSrc[0]);
        float g=lChannelConv(pSrc[1]);
        float b=lChannelConv(pSrc[2]);

        if(r==g&&r==b)
        {
            pDst[0]=0;
            pDst[1]=r;
            pDst[2]=0;
        }
        else if(r>g)
        {
            if(g>=b) //r,g,b
            {
                pDst[0]=(0+(g-b)/(r-b))/6.0f;
                pDst[1]=(r-b)/r;
                pDst[2]=r;
            }
            else if(b>r) //b,r,g
            {
                pDst[0]=(4+(r-g)/(b-g))/6.0f;
                pDst[1]=(b-g)/b;
                pDst[2]=b;
            }
            else //r,b,g
            {
                pDst[0]=(6+(g-b)/(r-g))/6.0f;
                pDst[1]=(r-g)/r;
                pDst[2]=r;
            }
        }
        else
        {
            if(r>b) //g,r,b
            {
                pDst[0]=(2+(b-r)/(g-b))/6.0f;
                pDst[1]=(g-b)/g;
                pDst[2]=g;
            }
            else if(b>g) //b,g,r
            {
                pDst[0]=(4+(r-g)/(b-r))/6.0f;
                pDst[1]=(b-r)/b;
                pDst[2]=b;
            }
            else //g,b,r
            {
                pDst[0]=(2+(b-r)/(g-r))/6.0f;
                pDst[1]=(g-r)/g;
                pDst[2]=g;
            }
        }
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgba,TColorSpace_hsv>
    :   public CColorSpaceConversion<TColorSpace_rgb,TColorSpace_hsv>
{
};

template <>
struct CColorSpaceConversion<TColorSpace_hsv,TColorSpace_rgb>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        CChannelConversion<TChannel_32f,typename tDst::TChannel> lChannelConv;
        
        float r,g,b;

        //If saturation is 0, the color is a shade of gray
        if(std::abs(pSrc[1])<0.0001f)
        {
            r=g=b=pSrc[2];
        }
        else
        {
            float h,s,v;
            h=pSrc[0];
            s=pSrc[1];
            v=pSrc[2];

            //to bring hue to a number between 0 and 6, better for the calculations
            h*=6.0f;
            uint32_t i=static_cast<uint32_t>(floor(h));
            h-=i;

            float p,q,t;
            p=v*(1.0f-s);
            q=v*(1.0f-s*h);
            t=v*(1.0f-s*(1.0f-h));

            switch( i )
            {         
                case 0: 
                    r=v;
                    g=t;
                    b=p;
                    break;

                case 1: 
                    r=q;
                    g=v;
                    b=p;
                    break;

                case 2: 
                    r=p;
                    g=v;
                    b=t;
                    break;

                case 3: 
                    r=p;
                    g=q;
                    b=v;
                    break;

                case 4: 
                    r=t;
                    g=p;
                    b=v;
                    break;

                case 5: 
                    r=v;
                    g=p; 
                    b=q;
                    break;
            }
        }

        pDst[0]=lChannelConv(r);
        pDst[1]=lChannelConv(g);
        pDst[2]=lChannelConv(b);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_hsv,TColorSpace_rgba>
    :   public CColorSpaceConversion<TColorSpace_hsv,TColorSpace_rgb>
{
};


template <>
struct CColorSpaceConversion<TColorSpace_rgb,TColorSpace_gray>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        typename tSrc::TChannel::TWorkingType r=pSrc[0],g=pSrc[1],b=pSrc[2],lGray;

        lGray=(r*2126+g*7152+b*722)/10000;

        pDst[0]=lChannelConv(lGray);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgba,TColorSpace_gray>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        typename tSrc::TChannel::TWorkingType r=pSrc[0],g=pSrc[1],b=pSrc[2],lGray;

        lGray=(r*2126+g*7152+b*722)/10000;

        pDst[0]=lChannelConv(lGray);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_gray,TColorSpace_rgb>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        typename tDst::TChannel::TType lResult=lChannelConv(pSrc[0]);
        pDst[0]=lResult;
        pDst[1]=lResult;
        pDst[2]=lResult;
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_gray,TColorSpace_rgba>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;

        typename tDst::TChannel::TType lResult=lChannelConv(pSrc[0]);
        pDst[0]=lResult;
        pDst[1]=lResult;
        pDst[2]=lResult;
        pDst[3]=tDst::TChannel::mMax;
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgb,TColorSpace_rgba>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[0]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[2]);
        pDst[3]=tDst::TChannel::mMax;
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgba,TColorSpace_rgb>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[0]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[2]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_bgr,TColorSpace_rgb>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgb,TColorSpace_bgr>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_bgra,TColorSpace_rgb>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgb,TColorSpace_bgra>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
        pDst[3]=tDst::TChannel::mMax;
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_rgba,TColorSpace_bgra>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
        pDst[3]=lChannelConv(pSrc[3]);
    }
};

template <>
struct CColorSpaceConversion<TColorSpace_bgra,TColorSpace_rgba>
{
    template <typename tSrc, typename tDst>
    void operator()(const tSrc& pSrc,tDst&& pDst) const
    {
        typename MGetChannelConversion<tSrc,tDst>::type lChannelConv;
        
        pDst[0]=lChannelConv(pSrc[2]);
        pDst[1]=lChannelConv(pSrc[1]);
        pDst[2]=lChannelConv(pSrc[0]);
        pDst[3]=lChannelConv(pSrc[3]);
    }
};

/*namespace img*/ } /*namespace buola*/ }

#endif
